package com.fub.update;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.content.Context;
import android.util.Log;

import com.fub.guidance.Edge;
import com.fub.guidance.RoomDataDAO;
import com.fub.guidance.RoutingGraphDAO;
import com.fub.guidance.Point;
import com.fub.guidance.Relation;
import com.fub.guidance.Vertex;
import com.fub.guidance.Wall;
import com.fub.guidance.BuildingDAO;
import com.fub.util.PreferenceDAO;

public class ServerStreamParser {

	private static final String DEBUG_TAG = "NetManager";
	private BuildingVersionMap buildingVersionMap;
	private String currentBuildingId;
	private String currentRoomId;
	private RoutingGraphDAO nodeEdgeDBService;
	private BuildingDAO wallDBService;
	private RoomDataDAO updateDBService;
	private List<Vertex> wallNodes;
	private PreferenceDAO preferenceDAO;
	private double longitude;
	private double latitude;

	/**
	 * Constructs a new server stream parser.
	 * 
	 * @param context
	 */
	public ServerStreamParser(Context context) {
		this.buildingVersionMap = new BuildingVersionMap();
		this.nodeEdgeDBService = new RoutingGraphDAO(context);
		this.wallDBService = new BuildingDAO(context);
		this.updateDBService = new RoomDataDAO(context);
		this.wallNodes = new ArrayList<Vertex>();
		this.preferenceDAO = new PreferenceDAO(context);
	}

	/**
	 * Parses and returns the versions of the buildings on server.
	 * 
	 * @return the versions of the buildings on server
	 */
	public BuildingVersionMap parseFromServerAndGetBuildingVersions()
			throws IOException {
		parseVersionInfo(getVersionServerStream());
		return this.buildingVersionMap;
	}

	/**
	 * Returns an input stream which contains the building data as XML.
	 * 
	 * @param buildingId
	 *            the id of the building
	 * @return an input stream which contains the building data as XML
	 */
	public InputStream getGraphStream(String buildingId) {
		try {
			URL uri = new URL(this.preferenceDAO.getServerName()
					+ "Controller?mode=graph&building=" + buildingId);
			return uri.openStream();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("cannot open server connection:" + e);
		}
	}

	/**
	 * Returns an input stream which contains the room data as XML.
	 * 
	 * @param buildingId
	 *            the id of the building
	 * @return an input stream which contains the room data as XML
	 */
	public InputStream getUpdateServerStream(String buildingId) {
		try {
			URL uri = new URL(this.preferenceDAO.getServerName()
					+ "Controller?mode=roomdata&building="
					+ buildingId);
			return uri.openStream();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("cannot open server connection:" + e);
		}
	}

	/**
	 * Returns an input stream which contains the versions of the buildings as
	 * XML.
	 * 
	 * @return an input stream which contains the versions of the buildings as
	 *         XML
	 * @throws IOException
	 */
	public InputStream getVersionServerStream() throws IOException {
		URL uri = new URL(this.preferenceDAO.getServerName()
				+ "Controller?mode=versions&longitude="
				+ this.longitude + "&latitude=" + this.latitude);
		return uri.openStream();
	}

	/**
	 * Parses the input stream and insert room data into database.
	 * 
	 * @param inputStream
	 *            the input stream which contains the data as XML.
	 */
	public void parseUpdates(InputStream inputStream) {
		try {
			XmlPullParser xmlPointer = XmlPullParserFactory.newInstance()
					.newPullParser();
			xmlPointer.setInput(inputStream, null);
			int eventType = -1;
			while (eventType != XmlPullParser.END_DOCUMENT) {
				if (eventType == XmlPullParser.START_TAG) {
					String tagName = xmlPointer.getName();
					if (tagName.equals("graph")) {
						this.currentBuildingId = xmlPointer
								.getAttributeValue(0);
					}
					if (tagName.equals("room")) {
						this.currentRoomId = xmlPointer.getAttributeValue(0);
						handleRoomTag(xmlPointer);
					}
				}
				eventType = xmlPointer.next();
			}

		} catch (Exception e) {
			Log.d(DEBUG_TAG, e.toString());
			e.printStackTrace();
		}
	}

	public void setLongitudeAndLatitude(double longitude, double latitude) {
		this.longitude = longitude;
		this.latitude = latitude;
	}

	private void handleRoomTag(XmlPullParser xmlPointer)
			throws XmlPullParserException, IOException {
		int eventType = xmlPointer.next();
		Map<String, String> properties = new HashMap<String, String>();
		while (eventType != XmlPullParser.END_DOCUMENT) {
			if (eventType == XmlPullParser.START_TAG) {
				if (xmlPointer.getName().equals("tag")) {
					String key = xmlPointer.getAttributeValue(0);
					String value = xmlPointer.getAttributeValue(1);
					properties.put(key, value);
				}

			} else if (eventType == XmlPullParser.END_TAG
					&& xmlPointer.getName().equals("room")) {
				this.updateDBService.insertRoomData(this.currentBuildingId,
						this.currentRoomId, properties);
				return;
			}
			eventType = xmlPointer.next();
		}
	}

	/**
	 * Parses the input stream and insert the buildings to versions control.
	 * 
	 * @param inputStream
	 *            the input stream which contains the data as XML
	 */
	public void parseVersionInfo(InputStream inputStream) {
		try {
			XmlPullParser xmlPointer = XmlPullParserFactory.newInstance()
					.newPullParser();
			xmlPointer.setInput(inputStream, null);
			int eventType = -1;
			while (eventType != XmlPullParser.END_DOCUMENT) {
				if (eventType == XmlPullParser.START_TAG) {
					String tagName = xmlPointer.getName();
					if (tagName.equals("building")) {
						handleBuildingTag(xmlPointer);
					}
				}
				eventType = xmlPointer.next();
			}

		} catch (Exception e) {
			Log.d(DEBUG_TAG, e.toString());
			e.printStackTrace();
		}
	}

	private void handleBuildingTag(XmlPullParser parser) {
		String buildingID = parser.getAttributeValue(0);
		int version = Integer.parseInt(parser.getAttributeValue(1));
		float latitude = Float.parseFloat(parser.getAttributeValue(2));
		float longitude = Float.parseFloat(parser.getAttributeValue(3));
		this.buildingVersionMap.addBuilding(buildingID, version, latitude,
				longitude);
	}

	/**
	 * Parses the input stream and insert nodes, edges, walls and relations into
	 * database.
	 * 
	 * @param inputStream
	 *            the input stream which contains the data as XML
	 */
	public void parseNodesEdgesWallsRelations(InputStream inputStream) {
		try {
			XmlPullParser xmlPointer = XmlPullParserFactory.newInstance()
					.newPullParser();
			xmlPointer.setInput(inputStream, null);
			int eventType = -1;
			while (eventType != XmlPullParser.END_DOCUMENT) {
				if (eventType == XmlPullParser.START_TAG) {
					String tagName = xmlPointer.getName();
					if (tagName.equals("graph")) {
						this.currentBuildingId = xmlPointer
								.getAttributeValue(0);
					}
					if (tagName.equals("node")) {
						handleNodeTag(xmlPointer);
					} else if (tagName.equals("edge")) {
						handleEdgeTag(xmlPointer);
					} else if (tagName.equals("wall")) {
						handleWallTag(xmlPointer);
					} else if (tagName.equals("relation")) {
						handleRelationTag(xmlPointer);
					}
				}
				eventType = xmlPointer.next();
			}

		} catch (Exception e) {
			Log.d(DEBUG_TAG, e.toString());
			e.printStackTrace();
		}
	}

	private void handleNodeTag(XmlPullParser parser) {
		String label = "";
		String id = parser.getAttributeValue(0);
		String type = parser.getAttributeValue(1);
		int x = Integer.parseInt(parser.getAttributeValue(2));
		int y = Integer.parseInt(parser.getAttributeValue(3));
		int z = Integer.parseInt(parser.getAttributeValue(4));

		if (parser.getAttributeCount() == 6) {
			label = parser.getAttributeValue(5);
		}
		Vertex vertex = new Vertex(id, this.currentBuildingId, new Point(x, y,
				z), type, label);
		if (type.equalsIgnoreCase("wall")) {
			this.wallNodes.add(vertex);
		}
		this.nodeEdgeDBService.insertNode(vertex);
	}

	private void handleRelationTag(XmlPullParser parser)
			throws XmlPullParserException, IOException {
		Relation relation = new Relation();
		String relationId = parser.getAttributeValue(0);
		String relationType = parser.getAttributeValue(1);
		List<String> nodes = new ArrayList<String>();
		int eventType = -1;
		while (eventType != XmlPullParser.END_DOCUMENT) {
			if (eventType == XmlPullParser.START_TAG) {
				String tagName = parser.getName();
				if (tagName.equals("member")) {
					String id = parser.getAttributeValue(0);
					nodes.add(id);
				}
			} else if (eventType == XmlPullParser.END_TAG
					&& parser.getName().equals("relation")) {
				relation.setId(relationId);
				relation.setBuildingID(this.currentBuildingId);
				relation.setType(relationType);
				relation.setNodes(nodes);
				this.nodeEdgeDBService.insertRelation(relation);
				return;
			}
			eventType = parser.next();
		}

	}

	private void handleEdgeTag(XmlPullParser parser) {
		String id = parser.getAttributeValue(0);
		String from = parser.getAttributeValue(1);
		String to = parser.getAttributeValue(2);
		Edge edge = new Edge(id, this.currentBuildingId, from, to, 0);
		this.nodeEdgeDBService.insertEdge(edge);
	}

	private void handleWallTag(XmlPullParser parser) {
		String from = parser.getAttributeValue(1);
		String to = parser.getAttributeValue(2);
		int x1 = getWallNode(from).getPosition().getX();
		int y1 = getWallNode(from).getPosition().getY();
		int x2 = getWallNode(to).getPosition().getX();
		int y2 = getWallNode(to).getPosition().getY();
		int z = getWallNode(from).getPosition().getZ();
		Wall wall = new Wall(this.currentBuildingId, x1, y1, x2, y2, z);
		this.wallDBService.insertWall(wall);
	}

	private Vertex getWallNode(String id) {
		for (Vertex node : this.wallNodes) {
			if (node.getId().equals(id)) {
				return node;
			}
		}
		throw new RuntimeException("wallnode not found:" + id);
	}

}
